# -*- coding: utf8 -*-
########################################################################
# Copyright (c) 2008 Burdakov Daniel <kreved@kreved.org>               #
#                                                                      #
# This file is part of PyWoW.                                          #
#                                                                      #
# PyWoW is free software: you can redistribute it and/or modify        #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or    #
# (at your option) any later version.                                  #
#                                                                      #
# PyWoW is distributed in the hope that it will be useful,             #
# but WITHOUT ANY WARRANTY; without even the implied warranty of       #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
# GNU General Public License for more details.                         #
#                                                                      #
# You should have received a copy of the GNU General Public License    #
# along with PyWoW.     If not, see <http://www.gnu.org/licenses/>.    #
########################################################################

from twisted.internet import protocol
from constants import *
from struct import pack, unpack, calcsize
import SRP
from hashlib import sha1
from hmac import HMAC
from opcodes import opcodes

class crypt:
 """этим будут шифроватьсяи и дешифроваться заголовки пакетов"""
 def __init__(self):
  self.recv_pos, self.send_pos, self.last_sent, self.last_received = 0, 0, 0, 0
  self.enabled = False
 def setKey(self, K):
  Ks = SRP.long_to_string(K)[::-1].ljust(40, '\x00')
  self.key = HMAC('\x38\xA7\x83\x15\xF8\x92\x25\x30\x71\x98\x67\xB1\x8C\x04\xE2\xAA', Ks, sha1).digest()
 def enable(self):
  self.enabled = True
 def encrypt(self, s):
  if self.enabled: return ''.join(map(self._encryptChar, s))
  else: return s
 def decrypt(self, s):
  if self.enabled: return ''.join(map(self._decryptChar, s))
  else: return s
 def _encryptChar(self, c):
  x = ord(c)
  self.send_pos %= len(self.key)
  x = ((x ^ ord(self.key[self.send_pos])) + self.last_sent) % 256
  self.send_pos += 1
  self.last_sent = x
  return chr(x)
 def _decryptChar(self, c):
  x = ord(c)
  self.recv_pos %= len(self.key)
  tmp = x
  x = ((256 + x - self.last_received) % 256) ^ ord(self.key[self.recv_pos])
  self.last_received = tmp
  self.recv_pos += 1
  return chr(x)

class WorldPacketProtocol(protocol.Protocol):
 """в этом классе находится всё, что будет использоваться и world-клиентом и сервером
 """
 host_opcode_fmt = None
 peer_opcode_fmt = None
 def connectionMade(self):
  self.pol = calcsize(self.peer_opcode_fmt)
  # sizeof(header) = sizeof(opcode) + sizeof(length), length is uint16
  self.phl = self.pol + 2
  self.buf = ''
  self.crypt = crypt()
  self.cached_header = None
 def dataReceived(self, data):
  """читаем из буфера calcsize(self.header_fmt) байт,
  находим opcode и длину пакета, после этого читаем из буфера тело пакета
  передаём функции packetReceived() opcode и тело пакета.
  при чтении из буфера учитывается, что может быть пакет получен ещё не полностью."""
  self.buf += data
  while len(self.buf) >= self.phl:
   # если в прошлый раз мы получили фрагментированный пакет,
   # то НЕЛЬЗЯ второй раз вызывать self.crypt.decrypt()!!!
   # поэтому смотрим header в кэше..
   # и только если он там не найден - тогда делаем decrypt()
   if self.cached_header is None: header = self.crypt.decrypt(self.buf[:self.phl])
   else: header = self.cached_header
   length = unpack(">H", header[:2])[0]
   opcode = unpack(self.peer_opcode_fmt, header[2:])[0]
   # sizeof(length) = sizeof(uint16) = 2
   total_length = length + 2
   if len(self.buf) < total_length:
    # пакет фрагментирован, сохраняем заголовок чтоб не расшифровывать повторно
    self.cached_header = header
    return
   # пакет получен полностью, удаляем кэшированный заголовок
   self.cached_header = None
   body = self.buf[self.phl:total_length]
   # remove unpacked data from buffer
   self.buf = self.buf[total_length:]
   self.packetReceived(opcode, body)
 def packetReceived(self, opcode, body):
  """Here should be some handler"""
  pass
 def handlePacket(self, opcode, body):
  self.factory.handlePacket(self, opcode, body)
 def send(self, opcode, body):
  length = len(body) + calcsize(self.host_opcode_fmt)
  header = pack(">H", length) + pack(self.host_opcode_fmt, opcode)
  packet = self.crypt.encrypt(header) + body
  self.transport.write(packet)
 def calcAuthDigest(self, username, sseed, cseed, K):
  s = username + '\x00' * 4 + cseed + sseed + SRP.long_to_string(K)[::-1]
  return SRP.hash(s)
